package com.meis.widget.ball;

import com.meis.widget.utils.AttrUtils;
import cs.help.tool.Log;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.render.PathMeasure;
import ohos.agp.utils.Color;
import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;

import java.security.SecureRandom;

import static ohos.agp.animation.Animator.CurveType.CUBIC_BEZIER_DECELERATION;
import static ohos.agp.animation.Animator.CurveType.LINEAR;

/**
 * Created by ccy on 2017-08-08.
 * 自己撸了一个 这里支持原著
 */
public class BounceBallView extends Component implements Component.DrawTask,
        Component.EstimateSizeListener {
    /**
     * 常量
     */
    private static final int CONSTANT = 160;
    private final float DEFAULT_BALL_RADIUS = dp2px(5);
    private static final int DEFAULT_BALL_COLOR = 0xff000000;
    private static final int DEFAULT_BOUNCE_COUNT = 2;
    private static final int DEFAULT_ANIM_DURATION = 2400;
    private static final int DEFAULT_BALL_COUNT = 10;
    private static final int DEFAULT_BALL_DELAY = (int) (DEFAULT_ANIM_DURATION / DEFAULT_BALL_COUNT);
    /**
     * 数据
     */
    private float radius;
    private int ballColor;
    private int bounceCount; //回弹次数  >=0
    private int ballCount;
    private int ballDelay; //当ballCount>1时，相邻小球开始下落的时间间隔
    private float defaultPadding;//默认偏移(left、top、right)
    private float defaultPaddingBottom; //bottom偏移（比默认偏移稍大一点）
    private int viewWidth;
    private int viewHeight;
    private float skipLength; //起点需无视的路径长
    /**
     * 绘图
     */
    private Paint[] paint;
    private Path path;
    private PathMeasure pathMeasure;
    private float[] pos = new float[2]; //存储某点的坐标值
    private float[] tan = new float[2]; //存储某点正切值
    private float[] segmentLength;
    private boolean isRandomBallPath = true; // 是否开启小球轨迹略微随机偏移
    private boolean isRandomColor = true; //是否开启小球随机颜色
    private boolean isRandomRadius = true; //是否开启小球随机大小（基础大小上下浮动）
    private float[] randomTransRatioX;
    private float[] randomTransRatioY;
    private int[] randomBallColors;
    private float[] randomRadius;
    private SecureRandom secureRandom;
    /**
     * 动画
     */
    private int defaultDuration;
    private boolean isPhysicsMode = true; //是否开启物理效果(下落加速，上弹减速）
    private AnimatorValue[] translateAnim; // 作用与小球位置变换  ValueAnimator
    private float[] translateFraction; //动画比例 [0,1]
    private boolean isRedraw = false;
    /**
     * 动态配置
     */
    //是否已开启动态配置事务
    private boolean isTransaction = false;

    public BounceBallView(Context context) {
        this(context, null);
    }

    public BounceBallView(Context context, AttrSet attrs) {
        this(context, attrs, 0);
    }

    public BounceBallView(Context context, AttrSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        secureRandom = new SecureRandom();
        if (attrs != null) {
            radius = AttrUtils.getFloatFromAttr(attrs, "ball_radius", DEFAULT_BALL_RADIUS);//默认值
            ballColor = AttrUtils.getColorFromAttr(attrs, "ball_color", DEFAULT_BALL_COLOR);
            bounceCount = AttrUtils.getIntegerFromAttr(attrs, "bounce_count", DEFAULT_BOUNCE_COUNT);
            defaultDuration = AttrUtils.getIntegerFromAttr(attrs, "anim_duration", DEFAULT_ANIM_DURATION);
            ballCount = AttrUtils.getIntegerFromAttr(attrs, "ball_count", DEFAULT_BALL_COUNT);
            ballDelay = AttrUtils.getIntegerFromAttr(attrs, "ball_delay", DEFAULT_BALL_DELAY);
            isPhysicsMode = AttrUtils.getBooleanFromAttr(attrs, "physic_mode", true);
            isRandomColor = AttrUtils.getBooleanFromAttr(attrs, "random_color", true);
            isRandomRadius = AttrUtils.getBooleanFromAttr(attrs, "random_radius", true);
            isRandomBallPath = AttrUtils.getBooleanFromAttr(attrs, "random_path", true);
        }
        //检查合法性
        checkAttrs();
        initData();
        initPath();
        setEstimateSizeListener(this);
        setBindStateChangedListener(new BindStateChangedListener() {
            @Override
            public void onComponentBoundToWindow(Component component) {
                addDrawTask(BounceBallView.this::onDraw);
            }

            @Override
            public void onComponentUnboundFromWindow(Component component) {
                cancel();
            }
        });
    }

    private void checkAttrs() {
        radius = radius >= 0 ? radius : DEFAULT_BALL_RADIUS;
        bounceCount = bounceCount >= 0 ? bounceCount : DEFAULT_BOUNCE_COUNT;
        ballCount = ballCount >= 1 ? ballCount : DEFAULT_BALL_COUNT;
        ballDelay = ballDelay >= 0 ? ballDelay : DEFAULT_BALL_DELAY;
        defaultDuration = defaultDuration >= 0 ? defaultDuration : DEFAULT_ANIM_DURATION;
    }


    private void initData() {
        defaultPadding = 2 * radius + dp2px(2);
        defaultPaddingBottom = 2 * radius + dp2px(15);
        paint = new Paint[ballCount];
        for (int i = 0; i < paint.length; i++) {
            paint[i] = new Paint();//Paint.ANTI_ALIAS_FLAG
            paint[i].setAntiAlias(true);
            paint[i].setColor(new Color(ballColor));
            paint[i].setStyle(Paint.Style.FILL_STYLE);//FILL
        }
        path = new Path();
        pathMeasure = new PathMeasure(path, false);// path,false为新增
        randomBallColors = new int[ballCount];
        randomRadius = new float[ballCount];
        randomTransRatioX = new float[ballCount];
        randomTransRatioY = new float[ballCount];
        translateFraction = new float[ballCount];
        translateAnim = new AnimatorValue[ballCount];

    }

    @Override
    public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
        int wSize = EstimateSpec.getSize(widthMeasureSpec);
        int hSize = EstimateSpec.getSize(heightMeasureSpec);
        viewWidth = wSize;
        viewHeight = hSize - 10;
        setEstimatedSize(viewWidth, viewHeight);
        initPath();
        return false;
    }

    /**
     * 初始化球体弹跳的路径
     */
    private void initPath() {
        path.reset();
        float intervalX = (viewWidth - 2 * defaultPadding) / (bounceCount + 1); //每次弹跳的间距
        PointF start = new PointF();//起点位置
        PointF control = new PointF(); //贝塞尔控制点
        PointF end = new PointF(); //贝塞尔结束点
        start.x = defaultPadding;
        start.y = viewHeight - defaultPaddingBottom;
        float controlOffsetY = viewHeight * 0.6F;  //控制点向上偏移量,0.6为调试值
        float deltaY = (1.2F * viewHeight + controlOffsetY) / (bounceCount + 1F); //控制点高度递减值，1.2为调试值
        PathMeasure tempPathMeasure = new PathMeasure(path, false);
        segmentLength = new float[bounceCount + 1];
        for (int i = 0; i <= bounceCount; i++) {
            control.x = start.x + intervalX * (i + 0.5F);
            control.y = -controlOffsetY + deltaY * i;
            end.x = start.x + intervalX * (i + 1);
            end.y = start.y;
            if (i == 0) {
                path.moveTo(start.x, start.y);
            }
            if (i == bounceCount) {
                end.y = viewHeight;
            }
            path.quadTo(control.x, control.y, end.x, end.y);
            tempPathMeasure.setPath(path, false);
            if (i == 0) { //第一次弹跳的上升阶段不画，记录弹跳一半长度(为效果更好，实际取值0.45
                skipLength = tempPathMeasure.getLength() * 0.45f;
            }
            segmentLength[i] = tempPathMeasure.getLength();
        }
        pathMeasure.setPath(path, false);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        drawBounceBall(canvas);
    }

    @Override
    public void addDrawTask(DrawTask task) {
        super.addDrawTask(task);
        task.onDraw(this, mCanvasForTaskOverContent);
    }

    private void drawBounceBall(Canvas canvas) {
        for (int i = 0; i < ballCount; i++) {
            if (isRedraw) {
                isRedraw = false;
                break;
            }
            canvas.restore();
            canvas.save();
            if (translateFraction[i] < (skipLength / pathMeasure.getLength())) {
                continue;
            }
            //根据当前动画进度获取path上对应点的坐标和正切
            pathMeasure.getPosTan(pathMeasure.getLength() * translateFraction[i], pos, tan);
            //路径随机
            if (isRandomBallPath) {
                pos[0] *= randomTransRatioX[i];
                pos[1] *= randomTransRatioY[i];
            }
            //颜色随机已在makeRandom里被应用
            canvas.drawCircle(pos[0],
                    pos[1],
                    isRandomRadius ? randomRadius[i] : radius,
                    paint[i]);
            canvas.restore();
        }
    }


    /**
     * 启动动画
     */
    public void start() {
        start(defaultDuration);
    }

    /**
     * 启动动画
     *
     * @param duration 动画时长
     */
    public void start(final int duration) {
        //放入队列（保证view已加载完成）
        createAnim(duration); //20170810备注：检查内部有没有重复创建实例
        startAnim();
    }

    public void runUI(Runnable runnable) {
        // 切换到主线程
        EventRunner runner = EventRunner.getMainEventRunner();
        EventHandler eventHandler = new EventHandler(runner);
        //切换任务
        eventHandler.postSyncTask(runnable);
    }

    private void startAnim() {
        for (int i = 0; i < translateAnim.length; i++) {
            translateAnim[i].start();
        }
    }

    private void createAnim(int duration) {
        int i = 0;
        for (i = 0; i < ballCount; i++) {
            createTranslateAnim(i, duration, i * ballDelay);
        }
    }

    private void createTranslateAnim(final int index, int duration, final int delay) {
        if (translateAnim[index] == null) {
            translateAnim[index] = new AnimatorValue();
            translateAnim[index].setDuration(Long.valueOf(duration + ""));
            translateAnim[index].setLoopedCount(Animator.INFINITE);//无限重复
            translateAnim[index].setDelay(delay);
            if (isPhysicsMode) {
                translateAnim[index].setCurveType(CUBIC_BEZIER_DECELERATION);//CUBIC_BEZIER_RHYTHM
            } else {
                translateAnim[index].setCurveType(LINEAR);
            }
            translateAnim[index].setStateChangedListener(new Animator.StateChangedListener() {
                @Override
                public void onStart(Animator animator) {
                    makeRandom(index);
                }

                @Override
                public void onStop(Animator animator) {

                }

                @Override
                public void onCancel(Animator animator) {

                }

                @Override
                public void onEnd(Animator animator) {

                }

                @Override
                public void onPause(Animator animator) {

                }

                @Override
                public void onResume(Animator animator) {

                }
            });
            translateAnim[index].setLoopedListener(new Animator.LoopedListener() {
                @Override
                public void onRepeat(Animator animator) {
                    makeRandom(index);
                }
            });
            translateAnim[index].setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float v) {
                    Log.i("translateFraction[" + index + "]");
                    if (null != translateAnim && index > translateAnim.length - 1) {
                        return;
                    }
                    translateFraction[index] = v;
                    Log.i("translateFraction[" + index + "]==" + v + "==ballCount==" + ballCount);
                    if (dealFromAlphaAnim(translateFraction[index]) != -1) {
                        paint[index].setAlpha(dealFromAlphaAnim(translateFraction[index]) / 255F);
                    } else if (dealToAlphaAnim(translateFraction[index]) != -1) {
                        paint[index].setAlpha(dealToAlphaAnim(translateFraction[index]) / 255F);
                    } else {
                        paint[index].setAlpha(255 / 255F);
                    }
                    invalidate();
                }
            });
        }
    }

    private void makeRandom(int index) {
        if (isRandomBallPath) {   //坐标是在ondraw里才获得的，故在ondraw里再去应用
            randomTransRatioX[index] = (float) (0.9F + (0.2F * secureRandom.nextDouble())); //[0.9,1.1)
            randomTransRatioY[index] = (float) (0.8F + (0.4F * secureRandom.nextDouble())); //[0.8,1.2)
        }
        if (isRandomColor) {  //不要在ondraw里再应用，会同时覆盖透明度通道，透明动画会失效
            randomBallColors[index] = getRandomColor();
            paint[index].setColor(new Color(randomBallColors[index]));
        } else {
            paint[index].setColor(new Color(ballColor));
        }
        if (isRandomRadius) {
            randomRadius[index] = (float) (radius * (0.7F + (0.6F * secureRandom.nextDouble()))); //[0.7,1.3]
        } else {
            randomRadius[index] = radius;
        }
    }
    /**
     * 传入一个值，和这个值的上下限，计算该值当前比例
     *
     * @param start   起点值
     * @param end     终点值
     * @param current 当前值
     * @return float
     * @throws RuntimeException 传值错误
     */
    private float getEvaluatedFraction(float start, float end, float current) {
        if (end - start == 0) {
            throw new RuntimeException("传值错误，分母为0: start = " + start + ";end = " + end);
        } else {
            return (current - start) / (end - start);
        }
    }

    /**
     * “透明-不透明”的透明值
     *
     * @param fraction fraction
     * @return int
     */
    private int dealFromAlphaAnim(float fraction) {

        if (null == segmentLength) return -1;

        float totalLength = pathMeasure.getLength();
        float beginFra = skipLength / totalLength;
        float endFrac = segmentLength[0] / totalLength;
        if (fraction > beginFra &&
                fraction < endFrac) {
            return (int) (255 * getEvaluatedFraction(beginFra, endFrac, fraction));
        }
        return -1;
    }

    /**
     * “透明-不透明”的透明值
     *
     * @param fraction fraction
     * @return int
     */
    private int dealToAlphaAnim(float fraction) {
        float totalLength = pathMeasure.getLength();
        if (null != segmentLength && segmentLength.length > 1) {
            float beginFra = segmentLength[segmentLength.length - 2] / totalLength;
            float endFrac = 1.0F;
            if (fraction > beginFra &&
                    fraction < 1.0F) {
                return (int) (255 - 255 * getEvaluatedFraction(beginFra, endFrac, fraction));
            }
        }
        return -1;
    }

    private int getRandomColor() {
        return Color.argb(255,
                secureRandom.nextInt(255),//255 * Math.random()),
                secureRandom.nextInt(255),
                secureRandom.nextInt(255));
    }

    /**
     * 取消已有动画，释放资源
     */
    public void cancel() {
        isRedraw = true;
        if (null != translateAnim) {
            for (int i = 0; i < translateAnim.length; i++) {
                if (translateAnim[i] != null) {
                    translateAnim[i].cancel();
                }
                translateAnim[i] = null;
            }
            translateAnim = null;
        }
    }

    /**
     * 开启配置事务，可连缀配置属性，最后调用{@link #apply()}使配置生效
     *
     * @return BounceBallView
     */
    public BounceBallView config() {
        isRedraw = true;
        for (int i = 0; i < translateAnim.length; i++) {
            if (null != translateAnim[i])
                translateAnim[i].cancel();
            translateAnim[i] = null;
        }
        isTransaction = true;
        return this;
    }

    /**
     * 使应用配置，在这之前先调用{@link #config()}
     */
    public void apply() {
        if (isTransaction == true) {
            Log.i("no config() function was called before calling apply()!");
        }
        isTransaction = false;
        checkAttrs();
        initData();
        initPath();
        postLayout();
        invalidate();
    }

    /**
     * 小球半径
     *
     * @param radius 默认5vp
     * @return BounceBallView
     */
    public BounceBallView radius(float radius) {
        check();
        this.radius = radius;
        return this;
    }

    /**
     * 小球颜色
     *
     * @param ballColor 默认黑色，{@link #isRandomColor} 为true时无效
     * @return BounceBallView
     */
    public BounceBallView ballColor(int ballColor) {
        check();
        this.ballColor = ballColor;
        return this;
    }

    /**
     * 小球数量
     *
     * @param ballCount 默认10
     * @return BounceBallView
     */
    public BounceBallView ballCount(int ballCount) {
        check();
        this.ballCount = ballCount;
        return this;
    }

    /**
     * 小球弹跳次数
     *
     * @param bounceCount 默认2次
     * @return BounceBallView
     */
    public BounceBallView bounceCount(int bounceCount) {
        check();
        this.bounceCount = bounceCount;
        return this;
    }

    /**
     * 相邻小球出现间隔
     *
     * @param ballDelay 默认为（动画时长/小球数量）。单位ms
     * @return BounceBallView
     */
    public BounceBallView ballDelay(int ballDelay) {
        check();
        this.ballDelay = ballDelay;
        return this;
    }

    /**
     * 一个小球一次完整的动画时长
     *
     * @param defaultDuration 默认2400ms。单位ms
     * @return BounceBallView
     */
    public BounceBallView duration(int defaultDuration) {
        check();
        this.defaultDuration = defaultDuration;
        return this;
    }

    /**
     * 是否颜色随机
     *
     * @param isRandomColor 默认true
     * @return BounceBallView
     */
    public BounceBallView isRandomColor(boolean isRandomColor) {
        check();
        this.isRandomColor = isRandomColor;
        return this;
    }

    /**
     * 是否路径稍微随机偏移
     *
     * @param isRandomBallPath 默认true
     * @return BounceBallView
     */
    public BounceBallView isRamdomPath(boolean isRandomBallPath) {
        check();
        this.isRandomBallPath = isRandomBallPath;
        return this;
    }

    /**
     * 小球大小是否稍微随机偏移
     *
     * @param isRandomRadius 默认true
     * @return BounceBallView
     */
    public BounceBallView isRandomRadius(boolean isRandomRadius) {
        check();
        this.isRandomRadius = isRandomRadius;
        return this;
    }

    /**
     * 重新
     *
     * @return BounceBallView
     */
    public BounceBallView isRedraw() {
        this.isRedraw = true;
        return this;
    }

    /**
     * 是否开启仿物理效果（下落加速上弹减速）
     *
     * @param isPhysicsMode 默认true
     * @return BounceBallView
     */
    public BounceBallView isPhysicMode(boolean isPhysicsMode) {
        check();
        this.isPhysicsMode = isPhysicsMode;
        return this;
    }

    private void check() {
        if (isTransaction) {
            return;
        } else {
            throw new RuntimeException("please call config() first to open the configuration and invoke apply() to apply the configuration");
        }
    }

    public float getRadius() {
        return radius;
    }

    public int getBallColor() {
        return ballColor;
    }

    public int getBounceCount() {
        return bounceCount;
    }

    public int getBallCount() {
        return ballCount;
    }

    public int getBallDelay() {
        return ballDelay;
    }

    public boolean isRandomBallPath() {
        return isRandomBallPath;
    }

    public boolean isRandomColor() {
        return isRandomColor;
    }

    public boolean isRandomRadius() {
        return isRandomRadius;
    }

    public int getDefaultDuration() {
        return defaultDuration;
    }

    public boolean isPhysicsMode() {
        return isPhysicsMode;
    }

    public float dp2px(float vp) {
        return (getContext().getResourceManager().getDeviceCapability().screenDensity / CONSTANT * Math.round(vp));
    }
}
